#include "glrenderer.h"
#include "common/utils.h"
#include "common/mlog.h"
#include <unistd.h>

#ifdef __cplusplus
extern "C" {
#endif

//#include <GLES2/gl2.h>
//#include <GLES2/gl2ext.h>
#include "common/mlog.h"
#include <OpenGLES/ES3/gl.h>

#ifdef __cplusplus
}
#endif

static const char* VERTEX_SHADER =
"attribute vec4 vPosition;    \n"
"attribute vec2 a_texCoord;   \n"
"varying vec2 tc;     \n"
"void main()                  \n"
"{                            \n"
"   gl_Position = vPosition;  \n"
"   tc = a_texCoord;  \n"
"}                            \n";
static const char* FRAG_SHADER =
"varying lowp vec2 tc;\n"
"uniform sampler2D SamplerY;\n"
"uniform sampler2D SamplerU;\n"
"uniform sampler2D SamplerV;\n"
"void main(void)\n"
"{\n"
"    mediump vec3 yuv;\n"
"    lowp vec3 rgb;\n"
"    yuv.x = texture2D(SamplerY, tc).r;\n"
"    yuv.y = texture2D(SamplerU, tc).r - 0.5;\n"
"    yuv.z = texture2D(SamplerV, tc).r - 0.5;\n"
"    rgb = mat3(1, 1, 1, 0, -0.39465,  2.03211, 1.13983, -0.58060, 0) * yuv;\n"
"    gl_FragColor = vec4(rgb, 1);\n"
"}\n";
//static const char* FRAG_SHADER =
//        "precision mediump float;\n"
//        "uniform sampler2D SamplerY;                 \n"
//        "uniform sampler2D SamplerU;                 \n"
//        "uniform sampler2D SamplerV;                 \n"
//        "varying vec2 tc;                         \n"
//        "void main()                                  \n"
//        "{                                            \n"
//        "  vec4 c = vec4((texture2D(SamplerY, tc).r - 16./255.) * 1.164);\n"
//        "  vec4 U = vec4(texture2D(SamplerU, tc).r - 128./255.);\n"
//        "  vec4 V = vec4(texture2D(SamplerV, tc).r - 128./255.);\n"
//        "  c += V * vec4(1.596, -0.813, 0, 0);\n"
//        "  c += U * vec4(0, -0.392, 2.017, 0);\n"
//        "  c.a = 1.0;\n"
//        "  gl_FragColor = c;\n"
//        "}                                            \n";

//    static GLfloat squareVertices[] = {
//        0.0f, 0.0f,
//        1.0f, 0.0f,
//        0.0f,  1.0f,
//        1.0f,  1.0f,
//    };
//
//    static GLfloat coordVertices[] = {
//            -1.0f, 1.0f,
//            1.0f, 1.0f,
//            -1.0f,  -1.0f,
//            1.0f,  -1.0f,
//    };

static GLfloat squareVertices[] = {
    -1.0f,  -1.0f,
    1.0f,  -1.0f,
    -1.0f,   1.0f,
    1.0f,   1.0f,
};


static GLfloat coordVertices[] = {
    0.0f,   1.0f,
    1.0f,   1.0f,
    0.0f,   0.0f,
    1.0f,   0.0f,
};



CVideoRenderer::CVideoRenderer()
: mProgramHandle(0), mViewportWidth(0), mViewportHeight(0)
{
}

CVideoRenderer::~CVideoRenderer()
{
    LOGE("video renderer free ...");
    uninitRenderer();
}

void CVideoRenderer::renderFrame(EAGLContext* context, int video_width, int video_height, int screen_width, int screen_height, uint8_t* frame)
{
    if (mProgramHandle == 0 || mViewportWidth != screen_width || mViewportHeight != screen_height) {
        LOGI("========> renderFrame: %dx%d, %dx%d, %dx%d",
             video_width, video_height, screen_width, screen_height, mViewportWidth, mViewportHeight);
        uninitRenderer();
        
        if ([EAGLContext currentContext] != context) {
            [EAGLContext setCurrentContext:context];
        }
        
        if (!initRenderer()) {
            LOGE("FAILED to init renderer!!!");
            return;
        }
        mViewportWidth = screen_width;
        mViewportHeight = screen_height;
        LOGI("Renderer init ok, viewport %dx%d, program %d", mViewportWidth, mViewportHeight, mProgramHandle);
    }
    
    
    glViewport(0, 0, mViewportWidth, mViewportHeight);
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    
    glUseProgram(mProgramHandle);
    
    if (frame != NULL) {
        bindTexture(mTextureYId, frame, video_width, video_height);
        bindTexture(mTextureUId, frame + video_width * video_height, video_width/2, video_height/2);
        bindTexture(mTextureVId, frame + video_width * video_height * 5 / 4, video_width/2, video_height/2);
    }
    
    //PRINTF("setsampler %d %d %d", mTextureYId, mTextureUId, mTextureVId);
    GLint tex_y = glGetUniformLocation(mProgramHandle, "SamplerY");
    GLint tex_u = glGetUniformLocation(mProgramHandle, "SamplerU");
    GLint tex_v = glGetUniformLocation(mProgramHandle, "SamplerV");
    
    glVertexAttribPointer(mPositionHandle, 2, GL_FLOAT, GL_FALSE, 0, squareVertices);
    glEnableVertexAttribArray(mPositionHandle);
    
    glVertexAttribPointer(mCoordHandle, 2, GL_FLOAT, GL_FALSE, 0, coordVertices);
    glEnableVertexAttribArray(mCoordHandle);
    
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, mTextureYId);
    glUniform1i(tex_y, 0);
    
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, mTextureUId);
    glUniform1i(tex_u, 1);
    
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, mTextureVId);
    glUniform1i(tex_v, 2);
    
    //glEnable(GL_TEXTURE_2D);
    //checkGlError("glEnable");
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    
    glFinish();
    
    glDisableVertexAttribArray(mPositionHandle);
    glDisableVertexAttribArray(mCoordHandle);
    
    if (context) {
        [context presentRenderbuffer:GL_RENDERBUFFER];
    }
}

bool CVideoRenderer::initRenderer()
{
    uninitRenderer();
    //create simpleProgram 前 一定要设置context
    //    [EAGLContext setCurrentContext:context];
    mProgramHandle = buildProgram(VERTEX_SHADER, FRAG_SHADER);
    glUseProgram(mProgramHandle);
    
    mPositionHandle = glGetAttribLocation(mProgramHandle, "vPosition");
    mCoordHandle = glGetAttribLocation(mProgramHandle, "a_texCoord");
    
    glGenTextures(1, &mTextureYId);
    glGenTextures(1, &mTextureUId);
    glGenTextures(1, &mTextureVId);
    LOGI("init opengles renderer. program %d", mProgramHandle);
    
    return (mProgramHandle != 0);
}

void CVideoRenderer::uninitRenderer()
{
    if (mProgramHandle != 0) {
        glDeleteProgram(mProgramHandle);
        mProgramHandle = 0;
        LOGI("uninit opengles");
    }
}

GLuint CVideoRenderer::bindTexture(GLuint texture, const uint8_t *buffer, GLuint w , GLuint h)
{
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, w, h, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, buffer);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
    return texture;
}

GLuint CVideoRenderer::buildShader(const char* source, GLenum shaderType)
{
    GLuint shaderHandle = glCreateShader(shaderType);
    
    if (shaderHandle){
        glShaderSource(shaderHandle, 1, &source, 0);
        glCompileShader(shaderHandle);
        
        GLint compiled = 0;
        glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compiled);
        if (!compiled) {
            GLint infoLen = 0;
            glGetShaderiv(shaderHandle, GL_INFO_LOG_LENGTH, &infoLen);
            if (infoLen) {
                glDeleteShader(shaderHandle);
                shaderHandle = 0;
            }
        }
    }
    
    return shaderHandle;
}

GLuint CVideoRenderer::buildProgram(const char* vertexShaderSource, const char* fragmentShaderSource)
{
    GLuint vertexShader = buildShader(vertexShaderSource, GL_VERTEX_SHADER);
    GLuint fragmentShader = buildShader(fragmentShaderSource, GL_FRAGMENT_SHADER);
    GLuint programHandle = glCreateProgram();
    
    if (programHandle) {
        glAttachShader(programHandle, vertexShader);
        glAttachShader(programHandle, fragmentShader);
        glLinkProgram(programHandle);
        
        GLint linkStatus = GL_FALSE;
        glGetProgramiv(programHandle, GL_LINK_STATUS, &linkStatus);
        if (linkStatus != GL_TRUE) {
            glDeleteProgram(programHandle);
            programHandle = 0;
        }
    }
    
    return programHandle;
}

//void CVideoRenderer::setRenderGLContent(EAGLContext* _context) {
//    context = _context;
////    scaleFactory = scale;
//}

